const toStr = (v) => Object.prototype.toString.call(v)
const isNotString = (v) => {
  if (!DuUtil.isString(v)) {
    console.error('正则参数必须是字符串！')
    return true
  } else {
    return false
  }
}
/**
 * 工具类集合，全部为静态方法
 * @exports CUtil
 * @class
 * @classdesc 工具类，主要提供类型判断，对象深度读取帮助函数如 a.b.c.d，深度赋值等方法。
 */
export default class DuUtil {
  /** **************************************************************** 类型判断 ********************************************************************************************/
  /**
   * 测试是否是Object
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是Object则返回true,否则返回false。
   */
  static isObject(v) {
    return toStr(v) === '[object Object]'
  }

  /**
   * 测试是否是字符串
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是字符串则返回true,否则返回false。
   */
  static isString(v) {
    return toStr(v) === '[object String]'
  }

  /**
   * 测试是否是Boolean类型
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是Boolean类型则返回true,否则返回false。
   */
  static isBoolean(v) {
    return toStr(v) === '[object Boolean]'
  }

  /**
   * 测试是否是日期类型
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是Date类型则返回true,否则返回false。
   */
  static isDate(v) {
    return toStr(v) === '[object Date]'
  }

  /**
   * 测试是否是function
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是function则返回true,否则返回false。
   */
  static isFunction(v) {
    return v instanceof Function
  }

  /**
   * 测试是否是Array
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是Array则返回true,否则返回false。
   */
  static isArray(v) {
    return toStr(v) === '[object Array]'
  }

  /**
   * 测试是否是Number
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是Number则返回true,否则返回false。
   */
  static isNumber(v) {
    return toStr(v) === '[object Number]'
  }

  /**
   * 测试是否是undefined
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是undefined则返回true,否则返回false。
   */
  static isUndefined(v) {
    return v === undefined
  }

  /**
   * 测试是否是Null
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是Null则返回true,否则返回false。
   */
  static isNull(v) {
    return v === null
  }

  /**
   * 测试是否是JSON字符串
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是JSON字符串则返回true,否则返回false。
   */
  static isJSONStr(v) {
    try {
      if (typeof JSON.parse(v) === 'object') {
        return true
      }
    } catch (e) {
      console.log(e)
    }
    return false
  }

  /**
   * 测试是否是空
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {boolean} 如果是undefined、null、空串、则返回true,否则返回false。
   */
  static isEmpty(v) {
    return DuUtil.isUndefined(v) || DuUtil.isNull(v) || DuUtil.trimAll(v) === ''
  }

  /** **************************************************************** 格式判断 ********************************************************************************************/

  /**
   * 验证手机号或座机号
   * @static
   * @function
   * @param {String} v - 待检测对象
   * @return {Boolean} 是否是手机号或座机号格式
   */
  static isTel(v) {
    if (isNotString(v)) {
      return false
    }
    return /^1[0-9]{10}$/.test(v) || /^[0-9]+-[0-9]+$/.test(v)
  }

  /**
   * 验证手机号
   * @static
   * @function
   * @param {String} v - 待检测对象
   * @return {Boolean} 是否是手机号格式
   */
  static isMobile(v) {
    if (isNotString(v)) {
      return false
    }
    return /^1[0-9]{10}$/.test(v)
  }

  /**
   * 验证身份证号
   * @static
   * @param {String} v - 待检测对象
   * @return {Boolean} 是否是身份证号格式
   */
  static isId(v) {
    if (isNotString(v)) {
      return false
    }
    let sId = v
    // let message = ''
    if (sId.length == 15) {
      if (!/^\d{14}(\d|x)$/i.test(sId)) {
        // message = '你输入的身份证长度或格式错误'
        return false
      } else {
        sId = sId.substr(0, 6) + '19' + sId.substr(6, 9)
        sId += getVCode(sId)
      }
    }
    function getVCode(CardNo17) {
      let Wi = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1)
      let Ai = new Array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2')
      let cardNoSum = 0
      for (let i = 0; i < CardNo17.length; i++) cardNoSum += CardNo17.charAt(i) * Wi[i]
      let seq = cardNoSum % 11
      return Ai[seq]
    }
    let aCity = {
      11: '北京',
      12: '天津',
      13: '河北',
      14: '山西',
      15: '内蒙古',
      21: '辽宁',
      22: '吉林',
      23: '黑龙江',
      31: '上海',
      32: '江苏',
      33: '浙江',
      34: '安徽',
      35: '福建',
      36: '江西',
      37: '山东',
      41: '河南',
      42: '湖北',
      43: '湖南',
      44: '广东',
      45: '广西',
      46: '海南',
      50: '重庆',
      51: '四川',
      52: '贵州',
      53: '云南',
      54: '西藏',
      61: '陕西',
      62: '甘肃',
      63: '青海',
      64: '宁夏',
      65: '新疆',
      71: '台湾',
      81: '香港',
      82: '澳门',
      91: '国外',
    }

    let iSum = 0
    if (!/^\d{17}(\d|x)$/i.test(sId)) {
      // message = '你输入的身份证长度或格式错误'
      return false
    }
    sId = sId.replace(/x$/i, 'a')
    if (aCity[parseInt(sId.substr(0, 2))] == null) {
      // message = '你的身份证地区非法'
      return false
    }
    let sBirthday = sId.substr(6, 4) + '-' + Number(sId.substr(10, 2)) + '-' + Number(sId.substr(12, 2))
    let d = new Date(sBirthday.replace(/-/g, '/'))
    if (sBirthday != d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()) {
      // message = '身份证上的出生日期非法'
      return false
    }
    for (let i = 17; i >= 0; i--) iSum += (Math.pow(2, i) % 11) * parseInt(sId.charAt(17 - i), 11)
    if (iSum % 11 != 1) {
      // message = '你输入的身份证号非法'
      return false
    }
    return true
  }

  /**
   * 验证邮箱
   * @static
   * @function
   * @param {String} v - 待检测对象
   * @return {Boolean} 是否是邮箱格式
   */
  static isPostCode(v) {
    if (isNotString(v)) {
      return false
    }
    return /^\d{6}$/.test(v)
  }

  /**
   * 验证大于等于0的自然数 0，1，2，3，4，……
   * * 如: 0个 1个...
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {Boolean} 是否是自然数
   */
  static isNatural(v) {
    if (DuUtil.isNumber(v)) {
      v = `${v}`
    }
    if (isNotString(v)) {
      return false
    }
    return /^[+]{0,1}(\d+)$/.test(v)
  }

  /**
   * 验证大于0的正整数 1，2，3，4，……
   * 如: 1个 2个...
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {Boolean} 是否是正数
   */
  static isPositive(v) {
    if (DuUtil.isNumber(v)) {
      v = `${v}`
    }
    if (isNotString(v)) {
      return false
    }
    return /^([1-9][0-9]*)$/.test(v)
  }

  /**
   * 验证大于等于0的数字，且小数点后不能大于n位
   * 如金额 123.45元 0元
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @param {Number} n - 小数点后n位
   * @return {Boolean} 是否是大于零的数字，小数点后不能大于n位
   */
  static isMoney(v, n = 2) {
    if (DuUtil.isNumber(v)) {
      v = `${v}`
    }
    if (isNotString(v)) {
      return false
    }
    const regExp = new RegExp(`(^[1-9](\\d+)?(\\.\\d{1,${`${n}`}})?$)|(^\\d\\.\\d{1,${`${n}`}}$)|(^0?$)`)
    return regExp.test(v)
  }

  /**
   * 验证大于0的数字,不能等于0，且小数点后不能大于n位
   * 如金额123.45元
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @param {Number} n - 小数点后n位
   * @return {Boolean} 是否是大于零的数字，小数点后不能大于n位
   */
  static isMoneyExclude0(v, n = 2) {
    if (DuUtil.isNumber(v)) {
      v = `${v}`
    }
    if (isNotString(v)) {
      return false
    }

    const regExp = new RegExp(`^([1-9]\\d*)(\\.\\d{1,${`${n}`}})?$|^0\\.\\d{1,${`${n}`}}?$`)
    return regExp.test(v)
  }

  /**
   * 验证n位数字字符串
   * 如编号001
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @param {Number} n - 位数
   * @return {Boolean} 是否是n位数字字符串
   */
  static isNumStr(v, n) {
    if (isNotString(v)) {
      return false
    }
    const regExp = new RegExp(`^\\d{${`${n}`}}$`)
    return regExp.test(v)
  }


  /**
   * 验证登录密码
   * 验证规则：密码必须是8~20位大小写字母、数字、特殊字符组合
   * @static
   * @function
   * @param {*} v - 待检测对象
   * @return {Boolean} 是否符合密码验证规则
   */
  static isPassword(v) {
    if (isNotString(v)) {
      return false
    }
    const regExp = new RegExp(`^(?=.*[0-9])(?=.*[A-Z])(?=.*[a-z])(?=.*[^(0-9a-zA-Z)]).{8,20}$`)
    return regExp.test(v)
  }

  /** **************************************************************** 小工具 ********************************************************************************************/

  /**
   * 判断是否是手机端
   * @static
   * @function
   * @param
   * @return {boolean}
   */
  static isMobileSys() {
    return /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone/i.test(
      navigator.userAgent
    )
  }

  /**
   * 深拷贝
   */
  static deepCopy(obj, hash = new WeakMap()) {
    if (obj instanceof Date) {
      return new Date(obj)
    }
    if (obj instanceof RegExp) {
      return new RegExp(obj)
    }
    if (hash.has(obj)) {
      return hash.get(obj)
    }
    let allDesc = Object.getOwnPropertyDescriptors(obj)
    let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
    hash.set(obj, cloneObj)
    for (let key of Reflect.ownKeys(obj)) {
      if (obj[key] && typeof obj[key] === 'object') {
        cloneObj[key] = DuUtil.deepCopy(obj[key], hash)
      } else {
        cloneObj[key] = obj[key]
      }
    }
    return cloneObj
  }

  /**
   * 去掉所有空白字符
   * @static
   * @function
   * @param {String} v - 待检测对象
   * @return {String} 去掉所有空白字符。
   */
  static trimAll(v) {
    if (DuUtil.isString(v)) {
      return v.replace(/\s/g, '')
    } else {
      return v
    }
  }

  /**
   * 转换千分符格式
   * @static
   * @function
   * @param {*} v - 待转换对象
   * @return {String} 转换千分符格式字符串。
   */
  static toFinanceNum(v) {
    if (DuUtil.isEmpty(v)) {
      return ''
    }
    if (DuUtil.isString(v)) {
      v = DuUtil.trimAll(v)
    }
    if (DuUtil.isNumber(v)) {
      const x = v * 100
      const y = x < 0 ? Math.ceil(x) : Math.floor(x)
      v = `${y / 100}`
    }
    const strArr = v.split('.')
    const num1 = strArr[0]
    let num2 = ''
    // 整数
    if (strArr.length === 1) {
      num2 = '00'
    }
    // 小数
    if (strArr.length > 1) {
      strArr[1] = strArr[1] + '00'
      num2 = strArr[1].substring(0, 2)
    }
    v = `${num1}.${num2}`

    return v.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
  }

  /**
   * 转换成大写财务文字
   * @static
   * @function
   * @param {*} v - 待转换对象
   * @return {String} 大写财务文字，如： 人民币壹仟贰佰叁拾陆元伍角捌分
   */
  static toFinanceUpperCaseStr(money) {
    const cny = '人民币'
    if (money === 0) {
      return `${cny}零元整`
    }
    if (!money) {
      return null
    }
    if (money === '') {
      return ''
    }

    const maxNum = 999999999999.9999 // 最大处理的数字
    if (money < 0) money = -money
    money = parseFloat(money)
    if (money >= maxNum) {
      console.error('超出最大处理数字')
      return ''
    }

    const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] // 汉字的数字
    const cnIntRadice = ['', '拾', '佰', '仟'] // 基本单位
    const cnIntUnits = ['', '万', '亿', '兆'] // 对应整数部分扩展单位
    const cnDecUnits = ['角', '分', '毫', '厘'] // 对应小数部分单位
    const cnInteger = '整' // 整数金额时后面跟的字符
    const cnIntLast = '元' // 整型完以后的单位
    let IntegerNum // 金额整数部分
    let DecimalNum // 金额小数部分
    let ChineseStr = '' // 输出的中文金额字符串
    let parts // 分离金额后用的数组，预定义

    money = money.toString() // 转换为字符串
    if (money.indexOf('.') === -1) {
      IntegerNum = money
      DecimalNum = ''
    } else {
      parts = money.split('.')
      IntegerNum = parts[0]
      DecimalNum = parts[1].substr(0, 4)
    }
    if (parseInt(IntegerNum, 10) > 0) {
      // 获取整型部分转换
      let zeroCount = 0
      const IntLen = IntegerNum.length
      for (let i = 0; i < IntLen; i++) {
        const n = IntegerNum.substr(i, 1)
        const p = IntLen - i - 1
        const q = p / 4
        const m = p % 4
        if (n === '0') {
          zeroCount++
        } else {
          if (zeroCount > 0) {
            ChineseStr += cnNums[0]
          }
          zeroCount = 0 // 归零
          ChineseStr += cnNums[parseInt(n, 10)] + cnIntRadice[m]
        }
        if (m === 0 && zeroCount < 4) {
          ChineseStr += cnIntUnits[q]
        }
      }
      ChineseStr += cnIntLast
      // 整型部分处理完毕
    }
    if (DecimalNum !== '') {
      // 小数部分
      const decLen = DecimalNum.length
      for (let i = 0; i < decLen; i++) {
        const n = DecimalNum.substr(i, 1)
        if (n !== '0') {
          ChineseStr += cnNums[Number(n)] + cnDecUnits[i]
        }
      }
    }
    if (ChineseStr === '') {
      ChineseStr += cnNums[0] + cnIntLast + cnInteger
    } else if (DecimalNum === '') {
      ChineseStr += cnInteger
    }
    return cny + ChineseStr
  }

  // 删除数组中的某个元素
  static arrayRemove(arr, v) {
    const index = arr.findIndex((item) => item === v)
    arr.splice(index, 1)
  }

  /**
   * 获取附件的扩展名
   * @static
   * @function
   * @param {String} v - 附件地址，或附件名
   * @return {String} - 附件的扩展名
   */
  static getExtName(v) {
    if (DuUtil.isString(v)) {
      let spl = v.split('.')
      return spl[spl.length - 1]
    } else {
      new Error('参数必须为string类型')
    }
  }

  /**
   * 判断文件名是否符合某些扩展名
   * @static
   * @function
   * @param {Array} r 扩展名数组，如[jpg, png, xls, xlsx ]
   * @param {String} v 文件名
   * @return {boolean} 判断文件名是否符合参数v中的格式
   */
  static isIncludeExt(r, v){
    const regExp = new RegExp(`\\.(${r.join("|")})$`, "i");
    return regExp.test(v);
  }

  /**
   * a标签可跨域下载图片文件 blob: URL方式下载
   * @static
   * @function
   * @param {String} url - 图片url链接地址
   * @param {String} name - 下载名称
   */
  static downloadByBlob(url, name) {
    let image = new Image() //创建图片对象
    image.setAttribute('crossOrigin', 'anonymous') //设置允许跨域
    image.onload = () => {
      //等待图片加载完成创建canvas
      let canvas = document.createElement('canvas')
      //将图片绘制到canvas画布上
      canvas.width = image.width
      canvas.height = image.height
      let ctx = canvas.getContext('2d')
      ctx.drawImage(image, 0, 0, image.width, image.height)
      //此处同样是利用canvas自带API将画布数据导出为bolb流格式
      canvas.toBlob((blob) => {
        let url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.download = name
        // 直接将canvas导出的bolb:URL 格式赋值为a标签的href属性 同样进行点击触发下载
        a.href = url
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
        // 释放URL对象
        URL.revokeObjectURL(url)
      })
    }
    image.src = url //赋值src
  }
  /**
   * a标签可跨域下载图片文件 fech方式下载
   * @static
   * @function
   * @param {String} url - 图片url链接地址
   * @param {String} name - 下载名称
   */
  static downloadByFetch(src, name) {
    src = src + '?t=' + new Date().getTime()
    fetch(src).then((response) => {
      response.blob().then((myBlob) => {
        const href = URL.createObjectURL(myBlob)
        let a = document.createElement('a')
        a.href = href
        a.download = name
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
      })
    })
  }
  /**
   * a标签可跨域下载图片文件 data:URL (base64格式)
   * @static
   * @function
   * @param {String} url - 图片url链接地址
   * @param {String} name - 下载名称
   */
  static downloadByData(url, name) {
    let image = new Image() //创建图片对象
    image.setAttribute('crossOrigin', 'anonymous') //设置允许跨域
    image.src = url //赋值src
    image.onload = () => {
      //等待图片加载完成创建canvas
      let canvas = document.createElement('canvas')
      //将图片绘制到canvas画布上
      canvas.width = image.width
      canvas.height = image.height
      let ctx = canvas.getContext('2d')
      ctx.drawImage(image, 0, 0, image.width, image.height)
      //获取图片文件格式  jpg/jpeg/png
      let ext = image.src.substring(image.src.lastIndexOf('.') + 1).toLowerCase()
      //导出画布快照  此时已越过跨域限制成功取到图片数据
      let dataURL = canvas.toDataURL('image/' + ext)
      //创建a标签
      const a = document.createElement('a')
      // 赋值a标签download属性值，标明该a标签提供下载功能，同时该属性指定了下载时的文件名称
      a.download = name
      //赋值a标签下载地址，即canvas导出的画布快照 base64格式的图片地址
      a.href = dataURL
      //添加a标签到dom中
      document.body.appendChild(a)
      //触发a标签点击事件 触发下载
      a.click()
      //下载完成 删除多余的a标签  保持页面原有元素不动
      document.body.removeChild(a)
    }
  }

  /** **************************************************************** 时间 ********************************************************************************************/

  /**
   * 判断v1时间是否比v2时间大
   * @static
   * @function
   * @param {String} v1 - 时间1 如：2011-04-16
   * @param {String} v2 - 时间2 如：2011-04-16
   * @return {Boolean} 判断时间1是否比时间2大
   */
  static isAfterDate(v1, v2) {
    const date1 = new Date(v1.replace(/-/g, '/'))
    const date2 = new Date(v2.replace(/-/g, '/'))
    return date1.getTime() > date2.getTime()
  }
  /**
   * Date格式转YYYY-MM-DD格式字符串
   * @static
   * @function
   * @param {Date} v - 日期
   * @return {String} - YYYY-MM-DD格式字符串
   */
  static DatetoYYYYMMDD(v) {
    const Y = v.getFullYear()
    const m = v.getMonth() + 1
    const M = m < 10 ? '0' + m : m
    const d = v.getDate()
    const D = d < 10 ? '0' + d : d
    return `${Y}-${M}-${D}`
  }
  /**
   * Date格式转YYYY-MM格式字符串
   * @static
   * @function
   * @param {Date} v - 日期
   * @return {String} - YYYY-MM格式字符串
   */
  static DatetoYYYYMM(v) {
    const Y = v.getFullYear()
    const m = v.getMonth() + 1
    const M = m < 10 ? '0' + m : m
    return `${Y}-${M}`
  }


  /** **************************************************************** 文件 ********************************************************************************************/

  // base64转blob
  static base64ToBlob(b64, type="image/jpeg"){
    const binary = atob(b64);
    const array = [];
    for (let i = 0; i < binary.length; i++) {
      array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], { type });
  }
  // blob转file
  static blobToFile(blob, fname){
    const file = new File([blob],
        `${fname}.jpeg`,
        { type: "image/jpeg" });
  }
}
